/*********************************************************************************
 *      Copyright:  (C) 2023 Lin XinCheng
 *                  All rights reserved.
 *
 *       Filename:  ecspi_oled.c
 *    Description:  This file for spi driver
 *                 
 *        Version:  1.0.0(2023年07月24日)
 *         Author:  Lin XinCheng <1481155734@qq.com>
 *      ChangeLog:  1, Release initial version on "2023年07月24日 21时09分37秒"
 *                 
 ********************************************************************************/

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>			//file_operations，用于联系系统调用和驱动程序
#include <linux/errno.h>		//ENODEV，ENOMEM存放的头文件
#include <linux/kernel.h>		// printk()，内核打印函数
#include <linux/device.h>		// 用于自动生成设备节点的函数头文件
#include <linux/gpio.h>			// gpio相关函数
#include <linux/gpio/consumer.h>// gpiod相关函数
#include <linux/of_gpio.h>		// gpio子系统相关函数
#include <linux/uaccess.h>		// copy_to_user函数头文件
#include <linux/timer.h>		//定时器相关
#include <linux/spi/spi.h>		//spi相关
#include <linux/delay.h>		// 延时函数头文件
#include <linux/cdev.h>			//cdev相关函数
#include <linux/mm.h>			//remap_pfn_range 
#include <linux/fb.h>
#include <linux/dma-mapping.h>
#include <linux/of_address.h>
#include <asm/mach/map.h>
#include <linux/slab.h>

#define DEV_NAME	"spi_oled"	//最后在/dev路径下的设备名称，应用层open的字符串名 
#define DEV_CNT		1

#define PLATDRV_MAGIC	0x16	//魔术字
#define OLED_ON			_IO (PLATDRV_MAGIC, 0x18)

unsigned char		*baseaddr;

#ifndef DEV_MAJOR
#define DEV_MAJOR 0
#endif

static int dev_major = DEV_MAJOR; //主设备号

int	dc_gpio;	//片选所使用的gpio编号
int reset_gpio;	//复位所使用gpio编号
int cs_gpio;	//数据，命令选择线所使用的gpio编号 

enum 
{
	OLED_CMD = 0,
	OLED_DATA,
};

/* OLED显示屏列数 */
#define X_WIDTH	128
#define	Y_WIDTH	64

/* 存放sht20的私有属性 */
struct oled_priv{
	struct cdev			cdev;
	struct class		*dev_class;
	struct spi_device	*spi;
	struct device		*dev;
};

/*
 * *向 oled 发送数据
 * *spi_device，指定oled 设备驱动的spi 结构体
 * *data, 要发送数据的地址
 * *lenght，发送的数据长度
 * */
 static int oled_send(struct spi_device *spi_device, unsigned char *buf, int lenght)
{
	int error = 0;
	int index = 0;
	struct spi_message *message;  //定义发送的消息
	struct spi_transfer *transfer;//定义传输结构体

	//使能oled，片选拉低
	gpio_set_value(cs_gpio, 0);

	//申请空间
	message = kzalloc(sizeof(struct spi_message), GFP_KERNEL);
	transfer = kzalloc(sizeof(struct spi_transfer),GFP_KERNEL);

	/* 每次发送 30字节，循环发送 */
	do
	{
		if(lenght > 30)
		{
			transfer->tx_buf = buf + index;
			transfer->len = 30;
			spi_message_init(message);
			spi_message_add_tail(transfer, message);
			index += 30;
			lenght -= 30;
		}
		else
		{
			transfer->tx_buf = buf + index;
			transfer->len = lenght;
			spi_message_init(message);
			spi_message_add_tail(transfer, message);
			index += lenght;
			lenght = 0;
		}
		error = spi_sync(spi_device, message);
		if(error != 0)
		{
			printk("spi_sync error! %d\n",error);
			return -1;
		}

	} while (lenght > 0);
	
	kfree(message);
	kfree(transfer);
	return 0;
}

/* OLED write a byte command data or command */
static void oled_send_one(struct oled_priv *priv, unsigned char data, unsigned short type)
{
	if(type)
	{
		gpio_set_value(dc_gpio, OLED_DATA);
	}
	else
	{
		gpio_set_value(dc_gpio, OLED_CMD);
	}
	
	oled_send(priv->spi, &data, 1);
	gpio_set_value(dc_gpio, OLED_DATA);
}

static int oled_set_pos(struct oled_priv *priv, uint8_t x, uint8_t y)
{
	oled_send_one(priv, 0xb0+y, OLED_CMD);            //设置行起始地址
	oled_send_one(priv, ((x&0xf0)>>4)|0x10, OLED_CMD);//设置低列起始地址
	oled_send_one(priv, (x&0x0f)|0X01, OLED_CMD);     //设置高列起始地址

	return 0;
}

/*
 * *填充整个OLED显示屏
 * *oled_priv *priv 私有数据
 * */
static void oled_clear(struct oled_priv *priv)
{
	uint8_t y,x;

	for(y=0;y<8;y++)
	{
		oled_set_pos(priv, 0 ,y);
		for(x=0;x<8;x++)
		{
			oled_send_one(priv, 0x00, OLED_DATA);
		}
	}
}

/*
 * *向oled发送显示数据， x,y指定显示的起始位置，支持自动换行
 * *spi_device,指定oled设备驱动的spi结构体
 * *display_buffer,数据地址
 * *length,发送长度
 * */
 static int oled_display_buffer(struct oled_priv *priv, uint8_t *display_buffer, int length)
{
	uint8_t y = 0;
	uint8_t x = 0;
	int index = 0;
	int error = 0;

	do
	{
		error += oled_set_pos(priv, x, y);
		if(length > (X_WIDTH - x))
		{
			error += oled_send(priv->spi, display_buffer + index, X_WIDTH - x);
			length -= (X_WIDTH - x);
			index += (X_WIDTH - x);
			x = 0;
			y++;
		}
		else
		{
			error += oled_send(priv->spi, display_buffer + index, length);
			index += length;
			// x += length;
			length = 0;
		}
	} while(length > 0);
	
	if(error != 0)
	{
		//发送错误
		printk("oled_display_buffer error! %d \n",error);
		return -1;
	}
	return index;
}

/* oled 初始化函数 */
static void oled_init(struct oled_priv *priv)
{
	gpio_set_value(reset_gpio, 1);
	mdelay(100);
	gpio_set_value(reset_gpio, 0);
	mdelay(200);
	gpio_set_value(reset_gpio, 1);

	oled_send_one(priv, 0xAE, OLED_CMD);//--turn off oled panel
	oled_send_one(priv, 0xFD, OLED_CMD);
	oled_send_one(priv, 0x12, OLED_CMD); 
	oled_send_one(priv, 0xd5, OLED_CMD);//--set display clock divide ratio/oscillator frequency
	oled_send_one(priv, 0xA0, OLED_CMD);    //Set COM Output Scan Direction
	oled_send_one(priv, 0xA8, OLED_CMD);//--set multiplex ratio(1 to 64)
	oled_send_one(priv, 0x3f, OLED_CMD);//--1/64 duty
	oled_send_one(priv, 0xD3, OLED_CMD);//-set display offset	Shift Mapping RAM Counter (0x00~0x3F)
	oled_send_one(priv, 0X00, OLED_CMD);//-not offset
	oled_send_one(priv, 0X40, OLED_CMD);//--set start line address  Set Mapping RAM Display Start Line (0x00~0x3F)
	oled_send_one(priv, 0XA1, OLED_CMD);//--Set SEG/Column Mapping     0xa0左右反置 0xa1正常
	oled_send_one(priv, 0XC8, OLED_CMD);//Set COM/Row Scan Direction   0xc0上下反置 0xc8正常
	oled_send_one(priv, 0xDA, OLED_CMD);//--set com pins hardware configuration
	oled_send_one(priv, 0x12, OLED_CMD);//
	oled_send_one(priv, 0x81, OLED_CMD);//--set contrast control register
	oled_send_one(priv, 0xBF, OLED_CMD);// Set SEG Output Current Brightness
	oled_send_one(priv, 0xD9, OLED_CMD);//--set pre-charge period
	oled_send_one(priv, 0x25, OLED_CMD);//Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
	oled_send_one(priv, 0xDB, OLED_CMD);//--set vcomh
	oled_send_one(priv, 0x34, OLED_CMD);//Set VCOM Deselect Level
	oled_send_one(priv, 0xA4, OLED_CMD);// Disable Entire Display On (0xa4/0xa5)
	oled_send_one(priv, 0xA6, OLED_CMD);// Disable Inverse Display On (0xa6/a7)
	oled_send_one(priv, 0xAF, OLED_CMD);// Disable Inverse Display On (0xa6/a7)
	oled_clear(priv);
}

/* 字符设备操作函数 open函数实现*/
static int oled_open(struct inode *inode, struct file *filp)
{
	struct oled_priv *priv = NULL;
	printk("oled open");
	
	priv = container_of(inode->i_cdev, struct oled_priv, cdev);

	filp->private_data = priv;
	oled_init(priv);
	printk("oled init\n");

	return 0;
}

static ssize_t oled_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
	int ret = 0;
	ret = copy_to_user(buf,baseaddr, 1024);
	return 0;
}

/* 字符设备操作函数，.write函数实现 */
static ssize_t oled_write(struct file *filp,const char __user *buf, size_t cnt, loff_t *off )
{
	struct oled_priv *priv = filp->private_data;
	unsigned char	databuf[1024];
	int 	rv = 0;

	//数据发送
	rv = copy_from_user(databuf, buf, cnt);
	if(rv < 0)
	{
		printk("oled_write failure\r\n");
		return -EFAULT;
	}
	printk("copy_from_user ok:cnt = %d\r\n",cnt);
	oled_display_buffer(priv, databuf, cnt);
	oled_clear(priv);
	return 0;
}

static int oled_mmap(struct file *filp,struct vm_area_struct *vma)
{
	vma->vm_page_prot = pgprot_cached(vma->vm_page_prot);

	if(!baseaddr)
	{
		return -1;
	}
	if(remap_pfn_range(vma, vma->vm_start, virt_to_phys(baseaddr) >> PAGE_SHIFT, vma->vm_end - vma->vm_start, vma->vm_page_prot))
	{
		return -ENOBUFS;
	}

	return 0;
}

static long oled_ioctrl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	struct oled_priv *priv = filp->private_data;
	unsigned char *display_buffer = baseaddr;
	switch(cmd)
	{
		case OLED_ON:
			oled_display_buffer(priv, display_buffer, 1024);
		break;
	}
	return 0;
}

static int oled_release(struct inode *inode, struct file *filp)
{
	struct oled_priv *priv = filp->private_data;
	oled_send_one(priv, 0xAE, OLED_CMD); //关闭显示

	return 0;
}

/* 字符设备操作函数集 */
static struct file_operations oled_fops = 
{
	.owner = THIS_MODULE,
	.open = oled_open,
	.read = oled_read,
	.write = oled_write,
	.unlocked_ioctl = oled_ioctrl,
	.mmap =	oled_mmap,
	.release = oled_release,
};

/* spi总线设备函数集:.probe函数只需要初始化esdpi,添加、注册一个字符设备即可。  */
static int oled_probe(struct spi_device *spi_dev)
{
	struct oled_priv	*priv = NULL;	//临时存放私有属性的结构体
	dev_t				devno;			//主次设备号
	int					rv = 0;
	struct	device_node *np = NULL;

	printk("match successed \n");

	/* 为oled私有属性分配存储空间 */
	priv = devm_kzalloc(&spi_dev->dev, sizeof(struct oled_priv),GFP_KERNEL);
	if(!priv)
		return -EINVAL;

	/* 获取oled的设备树节点*/
	np = of_find_node_by_path("/soc/bus@2000000/spba-bus@2000000/spi@2008000");//当前设备节点
	if( np == NULL)
	{
		printk("get ecspi_oled_node failure\n");
	}

	/* 获取 oled的cs引脚并设置为输出，默认高电平 */
	cs_gpio = of_get_named_gpio(np, "cs-gpio", 0);//请求gpio，有请求一定要有释放，否则模块下一次安装将请求失败
	if(cs_gpio < 0)
	{
		printk("can 't get cs_gpio\n");
		return -EINVAL;
	}
	rv = gpio_request(cs_gpio, "cs-gpio");
	rv = gpio_direction_output(cs_gpio, 1);
	if(rv < 0)
	{
		printk("can't set cs_gpio\n");
	}

	/* 获取 oled的reset引脚并设置为输出，默认高电平 */
	reset_gpio = of_get_named_gpio(np, "reset_gpio", 0); //请求gpio，有请求一定要有释放，否则模块下一次安装将请求失败
	if(reset_gpio < 0)
	{
		printk("can't get reset-gpio\n");
		return -EINVAL;
	}

	rv = gpio_request(reset_gpio, "reset-gpio");
	rv = gpio_direction_output(reset_gpio, 1);
	if(rv < 0)
	{
		printk("can't set reset_gpio\n");
	}

	/* 获取 oled的dc引脚并设置为输出，默认高电平 */
	dc_gpio = of_get_named_gpio(np, "dc-gpio", 0);//请求gpio，有请求一定要有释放，否则模块下一次安装将请求失败
	if(dc_gpio < 0)
	{
		printk("can't get dc-gpio\n");
		return -ENOMEM;
	}
	rv = gpio_request(dc_gpio, "dc-gpio");
	rv = gpio_direction_output(dc_gpio, 1);
	if(rv < 0)
	{
		printk("can't set dc-gpio!\r\n");
	}

	printk("cs_gpio=%d, reset_gpio=%d, dc_gpio=%d\n", cs_gpio, reset_gpio, dc_gpio);

	  /* ---------------------注册 字符设备部分----------------- */
	/* 1.分配主次设备号，这里即支持静态指定，也至此动态申请 */
	if( 0 != dev_major )
	{
		devno = MKDEV(dev_major, 0);
		rv = register_chrdev_region(devno, DEV_CNT, DEV_NAME);	/*  /proc/devices/DEV_NAME  */
	}
	else
	{
		rv = alloc_chrdev_region(&devno, 0, DEV_CNT, DEV_NAME);	/* 动态申请字符设备号*/
		dev_major = MAJOR(devno);	/* 获取主设备号 */
	}

	/* 2.分配cdev结构体，绑定主次设备号、fops到cdev结构体中，并注册给Linux内核 */
	priv->cdev.owner = THIS_MODULE; /* .owner这表示谁拥有你这个驱动程序 */
	cdev_init(&priv->cdev, &oled_fops); /* 初始化cdev,把fops添加进去*/ 
	rv = cdev_add(&priv->cdev, devno, DEV_CNT); /* 注册给内核,设备数量1个 */

	if( 0!=rv)
	{
		printk("%s driver can't register cdev:result=%d\n",DEV_NAME,rv);
		goto undo_major;
	}
	printk( " %s driver can register cdev:result=%d\n", DEV_NAME, rv);

	//3.创建类，驱动中进行节点创建
	priv->dev_class = class_create(THIS_MODULE, DEV_NAME);
	if(IS_ERR(priv->dev_class))
	{
		printk("%s driver create class failure\n",DEV_NAME);
		rv = -ENOMEM;
		goto undo_cdev;
	}

	//4.创建设备
	devno = MKDEV(dev_major, 0);//给每个led设置一个设备号
	priv->dev = device_create(priv->dev_class, NULL, devno, NULL, DEV_NAME);
	if(IS_ERR(priv->dev))
	{
		dev_err(&spi_dev->dev, "fail to create device\n");
		rv = -ENOMEM;   //返回错误码,应用空间strerror查看
		goto undo_class;
	}

	//5.初始化spi
	spi_dev->mode = SPI_MODE_0; //spi设置模式为SPI_MODE_0模式
	spi_dev->max_speed_hz = 2000000;  //设置最高频率，会覆盖设备树中的设置
	spi_setup(spi_dev);//设置spi
	priv->spi = spi_dev; //传回spi_device结构体，该结构体一个spi设备驱动的

	//6. 保存私有数据
	spi_set_drvdata(spi_dev, priv);

	dev_info(&spi_dev->dev, "oled spi,driver probe okay\n");
	return 0;

undo_class:
	class_destroy(priv->dev_class);

undo_cdev:
	cdev_del(&priv->cdev);

undo_major:
	unregister_chrdev_region(devno, DEV_CNT);

	return rv;
}

static int oled_remove(struct spi_device *spi_dev)
{
	//删除设备
	struct oled_priv *priv = spi_get_drvdata(spi_dev);//临时存放私有属性的结构体
	dev_t devno = MKDEV(dev_major, 0);

	devno = MKDEV(dev_major, 0);

	gpio_free(cs_gpio);
	gpio_free(reset_gpio);
	gpio_free(dc_gpio);

	device_destroy(priv->dev_class, devno);/* 注销每一个设备号 */

	class_destroy(priv->dev_class); //注销类
	cdev_del(&priv->cdev); //删除类
	unregister_chrdev_region(MKDEV(dev_major, 0), DEV_CNT);

	devm_kfree(&spi_dev->dev, priv); //释放堆

	kfree(baseaddr);
	printk("oled driver removed\n");
	return 0;
}

//定义设备树匹配表
static const struct of_device_id oled_of_match_table[] = {
	{.compatible = "oled_spi"},
	{},
};

//定义spi总线设备结构体
struct spi_driver oled_driver = {
	.probe = oled_probe,
	.remove = oled_remove,
	.driver ={
		.name = "oled_spi", //无设备树时，用于设备和驱动间匹配
		.owner = THIS_MODULE,
		.of_match_table = oled_of_match_table,
		},
};

//驱动初始化函数
static int __init oled_driver_init(void)
{
	int ret;
	printk("oled_driver_init\n");
	baseaddr = kmalloc(1024, GFP_KERNEL);
	ret = spi_register_driver(&oled_driver); //添加一个spi设备驱动
	return ret;
}

//驱动注销函数
static void __exit oled_driver_exit(void)
{
	printk("oled_driver_exit\n");
	spi_unregister_driver(&oled_driver); //删除一个spi设备驱动的
}

module_init(oled_driver_init);
module_exit(oled_driver_exit);

MODULE_AUTHOR("Linxincheng");
MODULE_DESCRIPTION("spi_oled_driver on imx6ull");
MODULE_LICENSE("GPL");
MODULE_ALIAS("spi_oled");




